package io.sundial.core.event;

import io.sundial.util.TypKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;

/**
 * 事件处理中心
 * 一个事件源的抽象实现
 *
 * @author Payne 646742615@qq.com
 * 2018/12/17 10:51
 */
public class EventSupport implements EventSource {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final ExecutorService executor;
    private final ConcurrentMap<Class<? extends Event>, ConcurrentMap<EventListener, Object>> binding = new ConcurrentHashMap<>();
    private final Object VALUE = new Object();

    /**
     * 缺省事件中心构造器，即触发执行的线程在当前调用的线程，需要注意的是如果某个事件监听器抛了异常将中断所有操作。
     */
    public EventSupport() {
        this(null);
    }

    /**
     * 指定触发执行器的构造器，事件监听的触发由传入的执行器来执行
     *
     * @param executor 事件监听触发执行器
     */
    public EventSupport(ExecutorService executor) {
        this.executor = executor;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void fire(Event event) {
        Class<? extends Event> eventType = event.getClass();
        while (true) {
            Map<EventListener, Object> set = binding.get(eventType);
            if (set == null) {
                set = Collections.emptyMap();
            }
            for (EventListener listener : set.keySet()) {
                try {
                    if (executor == null) {
                        listener.onListened(event);
                    } else {
                        executor.execute(new FireCommand(logger, listener, event));
                    }
                } catch (Exception e) {
                    logger.error("fail to fire event listener: " + listener + " for event: " + event, e);
                }
            }
            try {
                eventType = eventType.getSuperclass().asSubclass(Event.class);
            } catch (ClassCastException e) {
                return;
            }
        }
    }

    @Override
    public void addEventListener(EventListener<? extends Event> listener) {
        if (listener == null) {
            throw new NullPointerException("listener must not be null");
        }
        Type eventType = TypKit.derive(listener.getClass(), EventListener.class, 0);
        if (eventType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) eventType;
            eventType = parameterizedType.getRawType();
        }
        if (eventType instanceof Class<?>) {
            Class<? extends Event> key = ((Class<?>) eventType).asSubclass(Event.class);
            ConcurrentMap<EventListener, Object> set = new ConcurrentHashMap<>();
            ConcurrentMap<EventListener, Object> old = binding.putIfAbsent(key, set);
            if (old != null) {
                set = old;
            }
            set.put(listener, VALUE);
        } else {
            throw new IllegalArgumentException("could not derive the subscribed event type of this listener");
        }
    }

    @Override
    public void removeEventListener(EventListener<? extends Event> listener) {
        if (listener == null) {
            throw new NullPointerException("listener must not be null");
        }
        Type eventType = TypKit.derive(listener.getClass(), EventListener.class, 0);
        if (eventType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) eventType;
            eventType = parameterizedType.getRawType();
        }
        if (eventType instanceof Class<?>) {
            Class<? extends Event> key = ((Class<?>) eventType).asSubclass(Event.class);
            ConcurrentMap<EventListener, Object> set = binding.get(key);
            if (set == null) {
                return;
            }
            set.remove(listener);
        } else {
            throw new IllegalArgumentException("could not derive the subscribed event type of this listener");
        }
    }

    @Override
    public void shut() {
        if (executor != null) {
            executor.shutdown();
        }
    }

    protected static class FireCommand implements Runnable {
        private final Logger logger;
        private final EventListener listener;
        private final Event event;

        FireCommand(Logger logger, EventListener listener, Event event) {
            this.logger = logger;
            this.listener = listener;
            this.event = event;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            try {
                listener.onListened(event);
            } catch (Exception e) {
                logger.error("fail to fire event listener: " + listener + " for event: " + event, e);
            }
        }
    }
}
